Kalibrierbare Uhr

Quarze für Mikrocontroller haben eine Genauigkeit von etwa 10-4, damit eine für Quarzuhren inakzeptable Gangabweichung von 10 Sekunden pro Tag.

Wer einen genauen Frequenzzähler zur Verfügung hat, kann die Gangabweichung unter Kontrolle bringen:

Bei letzterer Methode ist gemeinsam mit einer Termperaturmessung und gemessenem Temperaturgang des Quarzes eine programmatische Temperaturkompensation möglich!

Aber wie erreicht man eine Genauigkeit von 10-6 bei lauter 8-bit-Teilern?
Irgendwelche Fummeleien mit umschaltenden Teilern sehen nicht gerade elegant aus...

Die Lösung ist ein genügend breiter Überlauf-Zähler. Wie funktioniert das?

  1. Eine Frequenzteilung bspw. durch 32 wird üblicherweise erreicht, indem man einen Zähler mit 32 initialisiert und so oft abwärts zählen lässt, bis dieser Null erreicht. Dann wird der Zähler wieder mit 32 initialisiert und die entsprechende Routine gerufen. Als Programm:
    	...
    	dec	r18	;r18 sei der ISR-Teiler
    	brne	ende
    	ldi	r18,32
    	call	SekundeVergangen
    ende:	...
    
    Damit wird ein 5-Bit-Zähler simuliert.

  2. Genauso geht es aber auch, auf einen 8-Bit-Zähler stets 8 zu addieren, und bei jedem Überlauf die Routine zu rufen:
    	...
    	ldi	r16,8
    	add	r18,r16	;r18 sei der ISR-Akkumulator
    	brcc	ende
    	call	SekundeVergangen
    ende:	...
    

  3. In Zeit gedacht ist die 8 anzusehen als eine Festkommazahl mit dem Wert 8/256 = 1/32, Sekunde nämlich.

  4. Nun ist es ohne weiteres möglich, den Akkumulator (hier r18) durch beliebig viele Bits zu ersetzen und eine entsprechend genaue Festkommazahl zu addieren.
Anfänger denken zu oft in Frequenzen, man arbeite besser stets mit Zeiten!

Ein Beispiel

Nehmen wir einen Atmel-Mikrocontroller mit 8 MHz.
Mit einem 8-bit-Zeitgeber und Vorteiler 1024 kommt man auf folgende Interruptperiodendauer:
Tint = 1024 * 256 / fosz,   fosz = 8000000 Hz
Erwischt! Mit Zeiten rechnen ist einfacher!
Tint = 1024 * 256 * Tosz, Tosz = 125 ns
Die Zeit beträgt in diesem Falle:
0,032768 s
Also eine Zahl, die kleiner als Eins (eine Sekunde) ist, und das ist gut so!
Jetzt brauchen wir nur noch ein bisschen Festkommaarithmetik, nehmen wir 32 bit für eine Auflösung von bis zu 10-9.

Dazu ermitteln wir einen (endlichen) Bruch, in dessen Nenner die computerfreundliche Zahl 232 steht, wir erweitern also um 232:

Tint = 1024 * 256 * Tosz * 232 / 232
Und fortan nehmen wir davon nur den (gerundeten) Zähler. Der ist eine riesige Ganzzahl und beträgt hier:
140737488 - hexadezimal 0x08637BD0
Noch einmal: Das ist der Zähler für den Binärbruch, in dessen Nenner 232 steht. Damit erreichen wir eine Auflösung von 1/140737488, also unter 10-8. Eine Uhr hätte damit eine Gangabweichung von 1 Sekunde in 4 Jahren. Das ist mit einem nicht temperaturstabilisierten Quarz, noch dazu am simplen Mikrocontroller, absolut illusorisch. 3 Bytes reichen auch: 1/(0x08637B+1) (Rundung) ergibt 1,8*10-6, 1 Sekunde in 6 Tagen.

Und was macht man nun damit?

Diese Konstante wird bei jedem Interrupt auf einen 4-Byte-Akkumulator addiert; bei jedem Überlauf ist eine Sekunde vergangen!
Dieser 4-Byte-Akkumulator beinhaltet die »Nachkomma-Sekunden« in 2-32tel Stückelung.

In Atmel-Worten:

.dseg
VierByteAkku:	.byte	4
.cseg
Timer0ISR:
	push	r16
	in	r16,sreg
	push	r16
	push	xl	;xh sei stillschweigend stets Null
	push	r4
	 ldi	xl,VierByteAkku
	 ld	r4,x
	 ldi	r16,$D0		;letztes Byte
	 add	r4,r16
	 st	x+,r4
	 ld	r4,x		; das schreit
	 ldi	r16,$7B		; nach
	 adc	r4,r16		; einem
	 st	x+,r4		; Makro
	 ld	r4,x
	 ldi	r16,$63
	 adc	r4,r16
	 st	x+,r4
	 ld	r4,x
	 ldi	r16,$08		;erstes Byte
	 adc	r4,r16
	 st	x+,r4
	pop	r4
	pop	xl
	jnc	ende
	call	SekundeVergangen
ende:	pop	r16
	out	sreg,r16
	pop	r16
	reti
Und das ist alles, was von der grauen Theorie übrig bleibt!

Jitter-Betrachtungen

Der Sekunden-Tick tritt alle 31..32 Zeitgeber-Interrupts auf. Die Programm-Logik sichert, dass es kein Wegdriften gibt. Dennoch gibt es »lange« und »kurze« Sekunden mit 3 % Unterschied; damit kann man also keinesfalls ein Referenz-Periodendauermessgerät speisen. Dagegen hilft nur die Verwendung einer höheren Interruptfrequenz (kleinerer Vorteiler, geringerer Jitter) mit angepasster Konstante.

Exakt gleich lange Sekunden können nur mit einem gezogenen Quarz erreicht werden.

Reentranz-Betrachtungen

Das Unterprogramm SekundeVergangen darf keinesfalls länger als knapp zwei Interruptperioden Zeit verbrauchen, denn verschluckte Interrupts führen zur falschen Uhrzeit!

Andererseits darf SekundeVergangen auch die Interrupts freigeben; darf dann allerdings nicht mehr als eine knappe Sekunde verbrauchen, alles bei voller sonstiger Interruptlast.
Andernfalls kommt es zur Reentranz (= ungewollte Rekursion). Dagegen helfen selbstgebastelte Semaphoren (siehe anderes Kapitel).

Kalibriervorgang

Sie benötigen dazu ein genügend genaues Frequenz/Periodendauermessgerät. Lassen Sie es nach Vorschrift/Bedienungsanleitung warmlaufen.

Ein Uhrenvergleich mit einer Funkuhr ist ein sehr langwieriges Unterfangen und sollte nur im Notfall nach guter Vorbereitung erfolgen. Als Messzeit sollte man 24 Stunden einplanen.

Die Quarzfrequenz darf keinesfalls direkt am Quarz gemessen werden, weil die Tastspitze mit ihrer (kapazitiven) Last unweigerlich den Quarz wegzieht.

Schreiben Sie in Ihre ISR Kode hinein, das ein Ausgabepin hin- und her schaltet (toggelt, halbe Frequenz beachten!) oder pulst.

Oder aber, geben Sie im Sekundentakt einen Puls aus. Hierbei ist der Jitter ist für das Kalibrieren katastrophal, deshalb benutzt man zunächst einen »geraden« Wert für die Konstante, bspw. 0x08000000.

Die Quarz-Periodendauer ergibt sich dann einfach zu:

Tosz = Tgemessen / Vorteiler / 256 ( / 32 für Sekundentakt)
Notieren Sie dabei die Temperatur der Schaltung.

Für eine Kennlinie brauchen Sie einen Gefrierschrank und einen Ofen, um für jede Temperatur (bspw. alle 5 °C) die Oszillatorperiodendauer zu messen.